package com.oxymore.jbnetapi.connectors;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.log4j.Logger;

import com.google.gson.Gson;
import com.oxymore.jbnetapi.core.ApiRequest;
import com.oxymore.jbnetapi.utils.Base64;
import com.oxymore.jbnetapi.utils.DateUtil;

public abstract class AbstractConnector
{
	private Logger logger = Logger.getLogger(AbstractConnector.class
			.getCanonicalName() + "\n");
	protected String baseUrl = null;
	protected String privateKey = null;
	protected String publicKey = null;
	protected boolean isUsingAuth = false;
	protected Gson gson = new Gson();

	public AbstractConnector(String baseUrl, String privateKey, String publicKey)
	{
		this.baseUrl = baseUrl;
		this.privateKey = privateKey;
		this.publicKey = publicKey;
	}

	protected InputStream getInputStream(URL requestUrl,
			Map<String, String> params) throws Exception
	{
		OutputStreamWriter wr = null;
		try
		{
			URLConnection conn = openConnection(requestUrl);
			conn.setDoOutput(true);
			wr = new OutputStreamWriter(conn.getOutputStream());
			StringBuilder data = new StringBuilder();
			for (Entry<String, String> entry : params.entrySet())
			{
				if (data.length() > 0)
					data.append("&");
				String key = entry.getKey();
				String value = entry.getValue();
				data.append(URLEncoder.encode(key, "UTF-8"));
				data.append("=");
				data.append(URLEncoder.encode(value, "UTF-8"));
			}
			wr.write(data.toString());
			wr.flush();
			return conn.getInputStream();
		} catch (Exception e)
		{
			throw e;
		} finally
		{
			if (wr != null)
				try
				{
					wr.close();
				} catch (IOException e)
				{
					e.printStackTrace();
				}
		}
	}

	protected URLConnection openConnection(URL requestUrl) throws Exception
	{
		URLConnection conn = null;
		try
		{
			conn = requestUrl.openConnection();
		} catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return conn;
	}

	/**
	 * 
	 * @param url
	 * @param authString
	 * @return
	 */
	protected String getContent(URL url, String authString)
	{
		HttpURLConnection conn = null;
		final StringBuilder contentResponse = new StringBuilder();
		String inputLine = null;
		logger.info("Trying to retrieve query content for url: "
				+ url.toString());
		try
		{
			conn = (HttpURLConnection) openConnection(url);
			conn.addRequestProperty("User-Agent", "JBnetApi");
			if (authString != null && isUsingAuth == true)
			{
				conn.addRequestProperty("Date", DateUtil.formatDate(new Date())
						.toString());
				conn.addRequestProperty("Authorization", authString);
			} else
				logger.info("No authString set, not using authentication");
			BufferedReader in = new BufferedReader(new InputStreamReader(
					conn.getInputStream()));

			while ((inputLine = in.readLine()) != null)
				contentResponse.append(inputLine);
			in.close();
			logger.info("Successfuly retrieved query content for url: "
					+ url.toString());
		} catch (Exception e)
		{
			logger.error("Failure while retrieving query content for url: "
					+ url.toString());
			BufferedReader in = new BufferedReader(new InputStreamReader(
					conn.getErrorStream()));
			try
			{
				while ((inputLine = in.readLine()) != null)
					contentResponse.append(inputLine);
				in.close();
			} catch (IOException e1)
			{
				logger.error("Failure while failing, I think it's bad.");
			}
		}
		return contentResponse.toString();
	}

	protected URL getURL(ApiRequest request)
	{

		Boolean firstparam = true;
		URL urlResult = null;

		StringBuilder result = new StringBuilder();
		result.append(request.getPath().getPath());
		if (request.getPage() != null)
			result.append(request.getPage().getPage());
		if (request.getCustomPage() != null)
			result.append(request.getCustomPage());
		String path = new String(result.toString());
		result = new StringBuilder();
		try
		{
			if (request.getParams() != null)
				for (Entry<String, String> entry : request.getParams()
						.entrySet())
				{
					if (result.length() > 0)
						if (firstparam)
							result.append("?");
						else
							result.append("&");
					String key = entry.getKey();
					String value = entry.getValue();
					result.append(key);
					result.append("=");
					result.append(value);
				}
			URI uriResult = new URI("https", getBaseUrl(), path,
					result.toString(), null);
			urlResult = uriResult.toURL();
		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return urlResult;
	}

	protected Map<String, String> getParams(ApiRequest request)
	{
		Map<String, String> result = new HashMap<String, String>();
		Map<String, String> params = request.getParams();
		if (params != null)
		{
			result.putAll(params);
		}
		return result;
	}

	/**
	 * 
	 * @param request
	 * @return
	 */
	protected String buildAuthorization(String request)
	{
		if (privateKey == null || publicKey == null)
		{
			logger.info("No signature generated");
			return null;
		}
		String date = new String(DateUtil.formatDate(new Date()));
		StringBuilder stringToSign = new StringBuilder();
		stringToSign.append("GET").append("\n");
		stringToSign.append(date.trim()).append("\n");
		stringToSign.append(request).append("\n");
		SecretKey key = new SecretKeySpec(privateKey.getBytes(Charset
				.forName("UTF-8")), "HmacSha1");
		Mac mac = null;
		try
		{
			mac = Mac.getInstance("HmacSha1");
			mac.init(key);
		} catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
		mac.update(stringToSign.toString().getBytes());
		StringBuilder result = new StringBuilder();
		result.append("BNET ").append(publicKey).append(":")
				.append(Base64.encodeBytes(mac.doFinal()));
		logger.info("Building authorization for:\n" + stringToSign);
		logger.info("Full signature: " + result.toString());
		return result.toString();
	}

	public String getBaseUrl()
	{
		return baseUrl;
	}

	public void setBaseUrl(String baseUrl)
	{
		this.baseUrl = baseUrl;
	}

	/**
	 * @return the privateKey
	 */
	public String getPrivateKey()
	{
		return privateKey;
	}

	/**
	 * @param privateKey
	 *            the privateKey to set
	 */
	public void setPrivateKey(String privateKey)
	{
		this.privateKey = privateKey;
		if (this.publicKey != null && this.privateKey != null)
			isUsingAuth = true;
	}

	/**
	 * @return the publicKey
	 */
	public String getPublicKey()
	{
		return publicKey;
	}

	/**
	 * @param publicKey
	 *            the publicKey to set
	 */
	public void setPublicKey(String publicKey)
	{
		this.publicKey = publicKey;
		if (this.publicKey != null && this.privateKey != null)
			isUsingAuth = true;
	}
}
